package com.three.api.protocol;

import com.three.protocol.CommandEnum;
import com.three.utils.StringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;

import java.io.Serializable;
import java.net.InetSocketAddress;
import java.util.Arrays;

/**
 * length(4)+cmd(2)+cc(2)+flags(1)+sessionId(4)+lrc(1)+body(n)
 *
 * @Author mathua
 * @Date 2017/5/19 14:52
 */
public class Packet implements Serializable{
    /**
     * 包头长度
     */
    public static final int HEADER_LEN = 14;

    /**
     * 加密标志位
     */
    public static final byte FLAG_CRYPTO = 1;
    /**
     * 压缩标志位
     */
    public static final byte FLAG_COMPRESS = 2;
    /**
     * 控制层(业务逻辑层)确认标志
     */
    public static final byte FLAG_BIZ_ACK = 4;
    /**
     * 自动确认标志
     */
    public static final byte FLAG_AUTO_ACK = 8;
    /**
     * json消息体标志
     */
    public static final byte FLAG_JSON_BODY = 16;

    public final short cmd; //命令
    transient public short cc; //校验码 暂时没有用到
    public byte flags; //特性，如是否加密，是否压缩等
    public int sessionId; // 会话id。客户端生成。
    transient public byte lrc; // 校验，纵向冗余校验。只校验head
    transient public byte[] body;
    private String bodyTmp;

    public static Packet build(CommandEnum.Command cmd, byte[] protoBody) {
        Packet packet = new Packet(cmd);
        packet.cc = 0;
        packet.flags = 0;
        packet.sessionId = 0;
        packet.lrc = 0;
        packet.body = protoBody;
        packet.bodyTmp = Arrays.toString(protoBody);
        return packet;
    }

    public Packet(CommandEnum.Command cmd) {
        this.cmd = (short) cmd.getNumber();
    }

    public Packet(CommandEnum.Command cmd, int sessionId) {
        this.cmd = (short) cmd.getNumber();
        this.sessionId = sessionId;
    }

    public Packet(short cmd) {
        this.cmd = cmd;
    }

    public Packet(short cmd, int sessionId) {
        this.cmd = cmd;
        this.sessionId = sessionId;
    }

    /**
     * 返回消息体长度
     *
     * @return
     */
    public int getBodyLength() {
        return body == null ? 0 : body.length;
    }

    /**
     * 加入标志信息
     *
     * @param flag 标志位
     */
    public void addFlag(byte flag) {
        this.flags |= flag;
    }

    /**
     * 判断是否拥有这个标志位
     *
     * @param flag
     * @return
     */
    public boolean hasFlag(byte flag) {
        return (flags & flag) != 0;
    }

    public <T> T getBody() {
        return (T) body;
    }

    public <T> void setBody(T body) {
        this.body = (byte[]) body;
    }

    /**
     * 计算检验码
     *
     * @return
     */
    public short calcCheckCode() {
        short checkCode = 0;
        if (body != null) {
            for (int i = 0; i < body.length; i++) {
                checkCode += (body[i] & 0x0ff);
            }
        }
        return checkCode;
    }

    /**
     * 计算lrc
     *
     * @return
     */
    public byte calcLrc() {
        byte[] data = Unpooled.buffer(HEADER_LEN - 1)
                .writeInt(getBodyLength())
                .writeShort(cmd)
                .writeShort(cc)
                .writeByte(flags)
                .writeInt(sessionId)
                .array();
        byte lrc = 0;
        for (int i = 0; i < data.length; i++) {
            lrc ^= data[i];
        }
        return lrc;
    }

    /**
     * 判断检验码是否合法
     *
     * @return
     */
    public boolean validCheckCode() {
        return calcCheckCode() == cc;
    }

    /**
     * 判断lrc码是否合法
     *
     * @return
     */
    public boolean validLrc() {
        return (lrc ^ calcLrc()) == 0;
    }

    public InetSocketAddress sender() {
        return null;
    }

    /**
     * 设置包的接收者
     *
     * @param sender
     */
    public void setRecipient(InetSocketAddress sender) {
    }

    /**
     * 响应包
     *
     * @param command
     * @return
     */
    public Packet response(CommandEnum.Command command) {
        return new Packet(command, sessionId);
    }

    public Object toFrame(Channel channel) {
        return this;
    }

    /**
     * 包解码
     *
     * @param packet     数据载体
     * @param in         包数据缓冲区
     * @param bodyLength 包体长度
     * @return
     */
    public static Packet decodePacket(Packet packet, ByteBuf in, int bodyLength) {
        packet.cc = in.readShort();//read cc
        packet.flags = in.readByte();//read flags
        packet.sessionId = in.readInt();//read sessionId
        packet.lrc = in.readByte();//read lrc

        //read body
        if (bodyLength > 0) {
            in.readBytes(packet.body = new byte[bodyLength]);
            System.out.println(Arrays.toString(packet.body));
        }
        return packet;
    }

    /**
     * 包编码
     *
     * @param packet 待编码数据包
     * @param out    编码后数据存储的缓冲区
     */
    public static void encodePacket(Packet packet, ByteBuf out) {
        out.writeInt(packet.getBodyLength());
        out.writeShort(packet.cmd);
        out.writeShort(packet.cc);
        out.writeByte(packet.flags);
        out.writeInt(packet.sessionId);
        out.writeByte(packet.lrc);
        if (packet.getBodyLength() > 0) {
            out.writeBytes(packet.body);
        }
        packet.body = null;
    }

    public int getSessionId() {
        return sessionId;
    }

    public Packet parseFromBodyTmp() {
        if(body == null && bodyTmp != null) {
            body = StringUtils.praseFromString(bodyTmp);
        }
        return this;
    }

    @Override
    public String toString() {
        return "{" +
                "cmd=" + cmd +
                ", cc=" + cc +
                ", flags=" + flags +
                ", sessionId=" + sessionId +
                ", lrc=" + lrc +
                ", body=" + (body == null ? 0 : body.length) +
                '}';
    }
}